<template>
  <div class="diyInputEdit">
    <div
      contenteditable="true"
      class="editDiv"
      :class="[
        isError ? 'editDiv--error' : '',
        !isContent ? 'editDiv--placeHolder' : '',
      ]"
      @input="handleInput"
      @keydown="handleKeyDown"
      :data-id="currentId"
      ref="editDiv"
    ></div>
    <div class="number">{{ current }}<span>/4000</span></div>
    <Popver v-if="popverShow" ref="popver"></Popver>
  </div>
</template>

<script>
import Mark from "mark.js";
import { v4 as uuidv4 } from "uuid";
import Popver from "../popver.vue";
import { createPopper } from "@popperjs/core";
export default {
  model: {
    prop: "inputValue", //定义什么 ， prop改成什么
    event: "changeInput", //事件是什么名字. $emit事件就是什么
  },
  props: {
    inputValue: {
      type: String,
      default: "",
    },
    suggestList: {
      type: Array,
      default: () => [],
    },
  },
  components: {
    Popver,
  },
  data() {
    return {
      isError: false,
      Mark: null,
      timeOut: null,
      watchTimeOut: null,
      current: 0,
      leftContent: "", //焦点左边的内容
      rightContent: "", //焦点右边的内容
      popverShow: false,
      popverTime: null,
      textMap: {}, //文本对应ucid
      textNumberMap: {}, //文本对应专利序号
      currentId: 0,
      currentOrderNumContent: {}, //当前序号对应信息
      history: [],
      historyPointer: 0,
      inputLock: false,
      // zeroWidth: "\u200D",
      zeroWidth: "wei",
    };
  },
  watch: {
    suggestList: {
      handler(nv, ov) {
        if (nv.length) {
          this.textMap = {};
          this.textNumberMap = {};
          this.suggestList.forEach((item) => {
            this.textMap[item.similarTechnicalPoints] = {
              uuidList: [],
            };
            if (this.textNumberMap[item.similarTechnicalPoints]) {
              this.textNumberMap[item.similarTechnicalPoints].push(
                item.orderNum
              );
            } else {
              this.textNumberMap[item.similarTechnicalPoints] = [item.orderNum];
            }
          });
          this.watchTimeOut & clearTimeout(this.watchTimeOut);
          this.watchTimeOut = setTimeout(() => {
            this.basicMark("outside");
          }, 300);
        }
      },
    },
  },
  computed: {
    isContent() {
      if (this.inputValue.trim()) {
        return true;
      } else {
        return false;
      }
    },
  },
  mounted() {
    this.$el.querySelector(".editDiv").innerHTML = this.inputValue;
    this.getCount();
    this.$el.addEventListener("mouseover", this.handleMouseover);
    this.$refs.editDiv.addEventListener("compositionstart", () => {
      this.inputLock = true;
    });
    this.$refs.editDiv.addEventListener("compositionend", () => {
      this.inputLock = false;
      this.handleInput(this.$refs.editDiv);
    });
  },

  methods: {
    async handleMouseover(e) {
      if (e.target.classList.contains("textNumber")) {
        clearTimeout(this.popverTime);

        this.popverShow = true;
        await this.$nextTick();
        createPopper(e.target, this.$refs.popver.$el, {
          placement: "bottom",
          modifiers: [
            {
              name: "offset",
              options: {
                offset: [0, 10],
              },
            },
          ],
        });
        let mouseLeave = (e1) => {
          let currentE = null;
          let over = (overE) => {
            currentE = overE;
          };
          window.addEventListener("mousemove", over);
          //一秒后 看看鼠标在不在浮窗内 , 在的话不关,不在的话关了
          this.popverTime = setTimeout(() => {
            if (
              this.$refs.popver &&
              this.$refs.popver.$el &&
              !this.$refs.popver.$el.contains(currentE.target)
            ) {
              this.popverShow = false;
              window.removeEventListener("mousemove", over);
            }
            window.removeEventListener("mousemove", over);
          }, 300);

          e.target.removeEventListener("mouseleave", mouseLeave);
        };
        e.target.addEventListener("mouseleave", mouseLeave);

        if (this.$refs.popver) {
          this.$refs.popver.$el.addEventListener("mouseleave", (e2) => {
            this.popverShow = false;
          });
        }
      }
    },
    handleKeyDown(e) {
      //ctrl +z
      if (e.ctrlKey && e.code == "KeyZ") {
        this.ctrlBack(e);
        return;
      } //ctrl + y
      else if (e.ctrlKey && e.code == "KeyY") {
        this.ctrlNext(e);
        return;
      }
      //回车上锁
      if (e.code == "NumpadEnter" || e.code == "Enter") {
        this.ctrlEnter(e);
        return;
      }
      //别影响基础操作
      if (
        e.code.indexOf("Arrow") != -1 ||
        e.code == "Backspace" ||
        e.code == "Delete"
      )
        return;

      let sel = window.getSelection();
      let range = sel.getRangeAt(0);
      if (
        range.endContainer.classList &&
        range.endContainer.classList.contains("textNumber")
      ) {
        console.log("符合1在当前标记节点之后");
        if (e.code == "Space") {
          e.preventDefault(); //不会触发handleInput了
          this.resetRange({
            afterDom: range.endContainer,
          });
        } else {
          this.$message.warning("请先输入空格在进行编辑");
        }
      } else if (
        range.endContainer.parentElement.classList.contains("textNumber")
      ) {
        console.log("符合2 说明是一个文本节点");
        if (e.code == "Space") {
          e.preventDefault();
          this.resetRange({
            afterDom: range.endContainer.parentElement,
          });
        } else {
          this.$message.warning("请先输入空格在进行编辑");
        }
      }
    },
    //撤回
    ctrlBack(e) {
      this.historyPointer -= 1;
      if (this.history.length == 0 || this.historyPointer < 0) {
        this.historyPointer = 0;
        e.preventDefault();
        return;
      }
      let res = this.history[this.historyPointer];
      this.$emit("changeInput", res.text);
      window.getSelection().removeAllRanges();
      this.$el.querySelector(".editDiv").innerHTML = res.range;
      e.preventDefault();
      this.ctrlInput();
    },
    //ctrl + y
    ctrlNext(e) {
      this.historyPointer += 1;
      if (this.historyPointer > this.history.length - 1) {
        this.historyPointer = this.history.length - 1;
        e.preventDefault();
        return;
      }
      let res = this.history[this.historyPointer];
      window.getSelection().removeAllRanges();
      this.$el.querySelector(".editDiv").innerHTML = res.range;
      this.$emit("changeInput", res.text);
      e.preventDefault();
      this.ctrlInput();
    },
    // 回车换行
    ctrlEnter(e) {
      if (this.inputLock) {
        return;
      }
      e.preventDefault(); //不会触发handleInput了
      let newLine = document.createElement("br");
      // 获取光标位置
      let selection = window.getSelection();
      let range1 = selection.getRangeAt(0);
      // 插入换行元素
      range1.insertNode(newLine);
      range1.setStartAfter(newLine);
      range1.setEndAfter(newLine);
      selection.removeAllRanges();
      selection.addRange(range1);
      let leftText = this.getCursorLeftText(this.$refs.editDiv);
      let rightText = this.getCursorRightText(this.$refs.editDiv);
      let range;
      let text = leftText + rightText;
      if (rightText.length == 0) {
        range = leftText + `<i class='place'></i><br>`;
      } else {
        range = leftText + `<i class='place'></i>` + rightText;
      }
      let res = {
        text: text,
        range: range,
      };
      this.$refs.editDiv.innerHTML = range;
      this.insetHistory(res);
      this.$emit("changeInput", res.text);
      this.resetRange({});
      this.getCount();
    },
    handleInput(e) {
      if (this.inputValue.length == 0) {
        this.$emit("changeInput", e.target.innerText);
      }
      if (this.inputLock) {
        return;
      }
      //存历史信息
      this.timeOut && clearInterval(this.timeOut);
      this.timeOut = setTimeout(() => {
        //存历史信息
        this.getText().then((res) => {
          this.insetHistory(res);
        });
        this.InputMark();
      }, 300);
    },
    insetHistory(res) {
      console.log(res, "res");
      this.$emit("changeInput", res.text);
      //最多存30个历史信息
      if (this.history.length > 30) {
        this.history.shift();
      }
      this.history.push(res);
      this.historyPointer = this.history.length - 1;
    },
    //ctrl导致的内容回退
    ctrlInput() {
      this.timeOut && clearInterval(this.timeOut);
      this.timeOut = setTimeout(async () => {
        this.currentId += 1;
        await this.$nextTick();
        let dom = this.$refs.editDiv.cloneNode(true);
        await this.startMark(dom, "input");
      }, 300);
    },
    //外部方法调用标记
    async basicMark(mark) {
      this.currentId += 1;
      await this.$nextTick();
      let dom = this.$refs.editDiv.cloneNode(true);
      await this.endMark(dom);
      await this.startMark(dom, mark);
    },
    //输入方法调用标记
    async InputMark() {
      if (this.inputLock) {
        return;
      }
      await this.basicMark("input");
      this.getCount();
      // console.log("标记");
    },
    //删除标记
    endMark(dom) {
      return new Promise((resolve, reject) => {
        // console.log(this.Mark, 11111);
        if (this.Mark) {
          // 删除文本序号
          let list = dom.querySelectorAll(".textNumber");
          list.forEach((item) => {
            item.remove();
          });
          // try {
          //页面有序号的时候
          let list1 = this.$refs.editDiv.querySelectorAll(".textNumber");
          list1.forEach((item) => {
            item.remove();
          });
        }
        try {
          this.leftContent = this.getCursorLeftText(this.$refs.editDiv);
          this.rightContent = this.getCursorRightText(this.$refs.editDiv);
          dom.innerHTML =
            this.leftContent + '<i class="place"></i>' + this.rightContent;
        } catch (err) {
          console.log(err);
        }

        resolve();
      });
    },
    // 获取光标左侧的全部文本
    getCursorLeftText(dom, mark) {
      let range = window.getSelection().getRangeAt(0); // 获取当前选择的范围
      let precedingText = range.cloneRange(); // 克隆范围
      // console.log(precedingText);

      let brList = dom.querySelectorAll("br");
      brList.forEach((item) => {
        let text = document.createTextNode(this.zeroWidth);
        item.before(text);
      });
      console.log(dom, 1111);
      // console.log(precedingText, "precedingText");
      if (!dom.innerText.trim()) {
        return "";
      }
      if (
        this.$refs.editDiv.contains(precedingText.commonAncestorContainer) ||
        (precedingText.commonAncestorContainer &&
          precedingText.commonAncestorContainer.classList &&
          precedingText.commonAncestorContainer.classList.contains("editDiv"))
      ) {
        precedingText.setStart(dom.firstChild, 0); // 选择整个 div 内容
        precedingText.setEnd(range.startContainer, range.startOffset); // 设置光标位置为范围开始的位置
        let frg = precedingText.cloneContents();
        console.log(frg, "frg");
        let text;
        // console.log(precedingText, "precedingText");
        if (mark) {
          let list = frg.querySelectorAll(".textNumber");
          list.forEach((item) => {
            item.remove();
          });
          text = frg.textContent;
        } else {
          text = precedingText.toString(); //拿不到br的
        }
        let reg = new RegExp(this.zeroWidth, "g");
        text = text
          .replace(/</g, "&lt;")
          .replace(/>/g, "&gt;")
          .replace(reg, "<br>");

        return text; // 获取光标左侧的内容
      }
      return this.$refs.editDiv.innerText;
    },
    // 获取光标右侧内容
    getCursorRightText(dom, mark) {
      let range = window.getSelection().getRangeAt(0); // 获取当前选择的范围
      let rightRange = range.cloneRange();
      if (!dom.innerText.trim()) {
        return "";
      }
      if (
        this.$refs.editDiv.contains(rightRange.commonAncestorContainer) ||
        (rightRange.commonAncestorContainer &&
          rightRange.commonAncestorContainer.classList &&
          rightRange.commonAncestorContainer.classList.contains("editDiv"))
      ) {
        rightRange.setStart(range.endContainer, range.endOffset);
        rightRange.setEnd(dom, dom.childNodes.length);
        // 获取临时div中的内容
        // console.log(rightRange.toString() ,'又');
        let frg = rightRange.cloneContents();
        let text;
        if (mark) {
          let list = frg.querySelectorAll(".textNumber");
          list.forEach((item) => {
            item.remove();
          });
          text = frg.textContent;
        } else {
          text = rightRange.toString(); //拿不到br的
        }

        let reg = new RegExp(this.zeroWidth, "g");
        text = text
          .replace(/</g, "&lt;")
          .replace(/>/g, "&gt;")
          .replace(reg, "<br>");
        let brList = dom.querySelectorAll("br");
        brList.forEach((item) => {
          item.remove();
        });
        console.log(text, "right");
        return text;
      }
      return "";
    },

    //开始标记
    startMark(dom, mark) {
      return new Promise(async (resolve, reject) => {
        this.Mark = new Mark(dom);
        for (let key in this.textMap) {
          await this.eachMark(key, this.textMap[key]);
        }
        for (let key in this.textMap) {
          let uuidList = this.textMap[key].uuidList;
          uuidList.forEach((item) => {
            let ele = dom.querySelector(`[uuid="${item.uuid}"]`);
            this.insertDom(ele, item.text);
          });
        }
        // console.log(dom.dataset.id, this.$refs.editDiv.dataset.id);
        // console.log(dom, "dom");
        if (dom.dataset.id == this.$refs.editDiv.dataset.id) {
          this.$refs.editDiv.innerHTML = dom.innerHTML;
          if (mark != "outside") {
            this.resetRange({});
          }
        }
        resolve();
      });
    },
    eachMark(text, obj) {
      return new Promise((resolve, reject) => {
        let currentText = "";
        let options = {
          element: "textNum",
          acrossElements: true,
          separateWordSearch: false,
          // exclude: [".textNumber" ,''],
          each: (ele) => {
            // console.log(ele, "ele");
            currentText += ele.innerText;
            //如果 标记的文本中 有隔开的dom元素,那有可能会被标记多个 , 会有多个序号标记点 , 为了处理这种情况 书写以下代码
            console.log(currentText, "currentText");
            console.log(text, "text");
            if (
              text.replace(
                /[\s.,\/#!$%\^&\*;:{}=\-_`~()（）“”‘’《》【】、，。？！；：]*/g,
                ""
              ) ==
              currentText.replace(
                /[\s.,\/#!$%\^&\*;:{}=\-_`~()（）“”‘’《》【】、，。？！；：]*/g,
                ""
              )
            ) {
              let uuid = uuidv4();
              ele.setAttribute("uuid", uuid);
              obj.uuidList.push({
                text,
                uuid,
              });
              currentText = "";
            }
          },
          done: (counter) => {
            resolve();
          },
        };
        // 定义要匹配的单词数组
        // var pattern = new RegExp(
        //   text.replace(/\s/g, "").split("").join("[\\s.,#!$%\^&;:{}=\-_`~()（）“”‘’《》【】、，。？！；：]*"),
        //   "gi"
        // ); // 使用数组中的单词构建正则
        var pattern = new RegExp(
          text
            .replace(
              /[\s.,\/#!$%\^&\*;:{}=\-_`~()（）“”‘’《》【】、，。？！；：]*/g,
              ""
            )
            .split("")
            .join("[\\s.,#!$%\^&;:{}=\-_`~()（）“”‘’《》【】、，。？！；：]*"),
          "gi"
        ); // 使用数组中的单词构建正则
        this.Mark.markRegExp(pattern, options);
        console.log(pattern, "pattern");
        // console.log(text, "text");
        // this.Mark.mark(text, options);
      });
    },
    insertDom(ele, text) {
      if (!ele) return;
      let orderNumber = this.textNumberMap[text];
      //插入对应的dom
      if (!orderNumber || !orderNumber.length) {
        return;
      }
      // console.log(orderNumber, "1111", ele);
      orderNumber
        .sort((a, b) => parseInt(b) - parseInt(a))
        .forEach((item) => {
          let span = document.createElement("span");
          span.classList.add("textNumber");
          span.innerText = item;
          span.setAttribute("data-orderNum", item);
          span.setStyle({
            background: "#3469E8",
            cursor: "pointer",
            marginLeft: "3px",
            fontSize: "14px",
            color: "#FFFFFF",
          });
          ele.after(span);
        });
    },

    //有rangePar的就是添加空格
    //重置选区
    resetRange({ afterDom }) {
      let rangeDom = null;
      //如果在序号之后键入内容 , 就会新建元素 ,防止后面字体颜色与序号同色
      if (afterDom) {
        let span = document.createElement("span");
        span.classList.add("placeholder");
        span.innerHTML = "&nbsp;";
        afterDom.after(span);
        rangeDom = span;
        let range = document.createRange();
        let sel = window.getSelection();
        sel.removeAllRanges();
        range.selectNode(rangeDom);
        range.collapse(false);
        sel.addRange(range);
      } else {
        let sel = window.getSelection();
        let range = document.createRange();
        let virDom = document.querySelector(".place");
        // console.log(virDom, "virDom");
        if (virDom) {
          sel.removeAllRanges();
          //此步骤的作用只是用来
          // 触发时机:文本输入后再走高亮的逻辑,如果走高亮前光标是紧跟在序号后
          // 判断:如果光标前一个元素是文本节点 ,那就把焦点放在文本节点后
          //如果光标是node节点并且element元素是textNumber, 就把焦点放在textnumber中的最后.这样可以触发下次输入添加空格
          if (
            virDom.previousSibling &&
            virDom.previousSibling.nodeType != 3 &&
            virDom.previousElementSibling &&
            virDom.previousElementSibling.classList.contains("textNumber")
          ) {
            // console.log("jinbu");
            range.setEnd(
              virDom.previousElementSibling,
              virDom.previousElementSibling.childNodes.length
            );
            range.collapse(false);
            sel.addRange(range); //选区 , 开头结尾一致的话 就是光标
          } else {
            // console.log("jinbu1");
            range.selectNode(virDom);
            sel.addRange(range); //选区 , 开头结尾一致的话 就是光标
          }
          virDom.remove();
        }
      }
    },
    //计算输入文本数量
    async getCount() {
      if (this.$el) {
        await this.$nextTick();
        let text = this.$refs.editDiv.innerText.trim().length;
        // console.log(text, "text");
        let list = this.$refs.editDiv.querySelectorAll(".textNumber");
        if (list) {
          list.forEach((item) => {
            text = text - item.innerText.length;
          });
        }
        this.current = text;
        return;
      }
      this.current = 0;
    },
    //为了记录历史内容
    async getText() {
      this.leftContent = this.getCursorLeftText(this.$refs.editDiv, "getText");
      this.rightContent = this.getCursorRightText(
        this.$refs.editDiv,
        "getText"
      );
      if (this.$el) {
        await this.$nextTick();
        // let dom = this.$refs.editDiv.cloneNode(true);
        // let list = dom.querySelectorAll(".textNumber");
        // list.forEach((item) => {
        //   item.remove();
        // });
        return {
          text: this.leftContent + this.rightContent,
          range: this.leftContent + '<i class="place"></i>' + this.rightContent,
        };
      }
    },
  },
  beforeDestroy() {
    this.$el.removeEventListener("mouseover", this.handleMouseover);
  },
};
</script>
<style scoped lang="less">
.diyInputEdit {
  width: 100%;
  height: 100%;
  flex: 1;
  min-height: 0;
  position: relative;
}
.editDiv {
  width: 100%;
  height: 100%;
  border: 1px solid #dce1e8;
  box-sizing: border-box;
  border-radius: 4px;
  outline: none;
  padding: 12px 8px;
  box-sizing: border-box;
  line-height: 22px;
  word-break: break-all;
  overflow-y: auto;
  padding-bottom: 24px;
  &:focus {
    border: 1px solid #1c4cba;
  }
}
.editDiv--error {
  border: 1px solid #f56c6c;
  &:focus {
    border: 1px solid #f56c6c;
  }
}
.editDiv--placeHolder {
  &::after {
    content: "请输入技术灵感";
    font-size: 14px;
    color: #c9cdd4;
    line-height: 16px;
  }
}
.number {
  position: absolute;
  right: 5px;
  bottom: 6px;
  font-weight: 400;
  font-size: 14px;
  color: #4e5969;
  span {
    color: #c1c7cc;
  }
}
/deep/ textnum *:not(.textNumber) {
  text-decoration: underline;
  text-decoration-color: #366ef4;
  text-underline-offset: 5px;
  color: #739af8;
}
/deep/ h1 {
  color: red;
}
// .parent *:not(.child) {
//     text-decoration: underline;
// }
</style>
